/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/
/**
 * 三种状态的常量定义
 */
const PENDING = 'pending';  //等待
const FULFILLED = 'fulfilled';   //成功
const REJECTED = 'rejected';  //
/**
 * 系统的Promise传统用法如下：
 * const promise = new Promise((resolve, reject) => {
 *      resolve('成功')  //这里有传值操作，并且能在then操作死拿到该值
 *      // reject('失败')
 * })
 * 
 * 思路：
 * 1. Promise是一个类，通过new操作符创建promise对象，创建时需要传入一个执行器进去，该执行器会立即执行，
 * 2. Promise对象由三种状态，分别为pending(等待)，fulfilled(成功)，和rejected(失败)。
 * 同一个promise对象中，这三种状态的转换只能是：
 *      pending -> fulfilled；
 *      pending -> rejected;
 * 且其状态变化不可逆转。
 * 3. resolve和reject函数是用来更改状态的:
 *      resolve被调用时 -> promise对象状态变为：fulfilled；
 *      reject被调用时 -> promise对象状态变为：rejected；
 * 
 * 传统then的用法分析：promise.then(value=> {}, reason => {})
 * 4. then方法内部做的事情就是状态判断，
 *      - 如果状态是成功就调用成功的回调，
 *      - 如果是失败就调用失败的回调函数。
 * 
 * 5. then的成功回调里面有个参数，表示成功后的值；then的失败回调里面也有一个参数，表示失败后的原因
 * 所以，promise实例里需要有对应属性记录成功后的值和失败后的原因。
 * 
 * 6. then可能再同一个promise实例上多次调用，这就会产生一个回调队列，
 * 所以回调属性可以设置成数组，以保存then被多次调用情况下产生的多个回调函数
 * 
 * 7. then方法是可以链式调用的，每次的返回值都是一个结果值或者promise对象，
 * 它的最终返回都必须是一个新的promise实例，它是可以调用then方法的
 * 
 * 8. promise不能产生自调用，这种情况需要被检测出来进行异常处理
 */
class MyPromise {
    // 状态，初始化默认状态为等待
    status = PENDING;
    // 成功的值，初始值为undefined
    value = undefined;
    // 失败的原因，初始值为undefined
    reason = undefined;
    // 为处理一个promise实例上多次调用then的情况，所以这里使用数组作为回调函数的存储队列，队列尾部插入push，头部逐个取出shift
    // 成功回调
    // successCallback = undefined;
    successCallback = [];
    // 失败回调
    // failCallback = undefined;
    failCallback = [];

    // 暂存promise链路中的resolve/reject的状态，用以在then传入非function回调的时候续传对应状态下的value
    // pengdingResolves = [];
    // pengdingRejects = [];
    /**
     * 对任意值进行Promise化，封装成promise对象
     * @param {给定任意值} value 
     * @returns 
     */
    static resolve(value) {
        // 如果已经是promise对象，那就直接返回该值
        if (value instanceof MyPromise) return value;
        // 否则创建一个新的promise对象并在对象的执行器中使用resolve返回该值
        return new MyPromise(resolve => resolve(value))
    }
    /**
     * 该构造函数需要传入一个执行器进去，该执行器会立即执行
     * @param {执行器} excutor 
     */
    constructor(excutor) {
        try {

            let isFunc = excutor instanceof Function
            const preditResolverAsFunction = (excutor) => {
                throw new TypeError(`Promise resolver ${excutor} is not a function`)
            }
            // 这里对执行器参数的数据类型进行判断，，要求执行器是一个function，否则抛出对应的类型错误说明
            isFunc ? excutor(this.resolve, this.reject) : preditResolverAsFunction(excutor)
        } catch (error) {
            this.reject(error)
        }
    }
    /**
     * 给定任意值，返回该值的一个resolved状态的Promise对象
     * 该方法主要作用是改变Promise的状态，从等待处理状态变为成功状态
     * @param {*} value 
     */
    resolve = (value) => {
        // 成功回调时，状态需改变为成功状态
        if (this.status != PENDING) return;//这里处理状态不可逆情况，如果状态不在是pending，那就不做任何操作
        this.status = FULFILLED
        // 保存成功之后的值
        this.value = value
        // 判断成功回调是否存在，存在则调用成功回调,并把成功值value值传进去，失败情况同理
        // this.successCallback&&this.successCallback(this.value)
        // 循环数组逐个进行回调处理
        // let cb = undefined;
        // let resolveTmp = undefined;
        // let rejectTmp = undefined;
        while (this.successCallback.length) {
            // 这里需要对传入的暂存的回调函数队列中的元素进行处理，以防非函数进行调用。
            // cb = this.successCallback.shift()
            this.successCallback.shift()()
            // resolveTmp = this.pengdingResolves.shift()
            // rejectTmp = this.pengdingRejects.shift()
            // resolvePromise(this.value, resolveTmp, [rejectTmp])
            // if (cb instanceof Function) cb(this.value)
            // cb()
            // else this.pengdingResolves[idx](this.value)
        }
    }
    /**
     * 给定任意值（一般是错误描述字符串或者一个错误对象），返回该值的一个rejected状态的Promise对象
     * 该方法主要作用是改变Promise的状态，从等待处理状态变为失败状态
     * @param {*} reason 
     * @returns 
     */
    reject = (reason) => {
        // 失败回调时，状态需改变为失败状态
        if (this.status != PENDING) return;//这里处理状态不可逆情况，如果状态不在是pending，那就不做任何操作
        this.status = REJECTED
        // 保存失败后的原因
        this.reason = reason
        // 判断失败回调是否存在，存在则调用失败回调,并把失败原因reason传进去
        // this.failCallback&&this.failCallback(this.reason)
        // 循环数组逐个进行回调处理
        // let cb = undefined;
        while (this.failCallback.length) {
            // cb = this.failCallback.shift()
            this.failCallback.shift()()
            // 这里需要对传入的暂存的回调函数队列中的元素进行处理，以防非函数进行调用。
            // cb instanceof Function ? cb(this.reason) : console.warn(`UnhandledPromiseRejectionWarning: Unhandled promise rejection.`)
            // cb()
        }
    }
    /**
     * then中需要传递两个回调函数，分别是成功时的回调以及失败时的回调
     * then可能再同一个promise实例上多次调用，这就会产生一个回调队列
     * @param {*} successCallback 
     * @param {*} failCallback 
     */
    //原生Promise对象实例的then方法里可以传任意参数，并且针对函数进行回调，其他值直接忽略，并返回上一次then调用的返回值，这个该如何处理
    then(successCallback, failCallback) {
        // let isFunc;
        // 这里我们处理多次then参数忽略情况下的传值，这里通过构造默认传值回调函数来实现
        successCallback = successCallback && successCallback instanceof Function ? successCallback : value => value;
        failCallback = failCallback && failCallback instanceof Function ? failCallback : reason => { throw reason };
        // 把代码封装到一个全新的Promise实例中返回
        let promiseTemp = new MyPromise((resolve, reject) => {
            // 状态判断
            switch (this.status) {
                case FULFILLED:

                    // isFunc = successCallback instanceof Function

                    // 调用成功回调时同时把成功的值传进去
                    // 若果不是function，就直接把上一个promise的结果值续传下去
                    // let sc = isFunc ? successCallback(this.value) : this.value
                    // let sc = successCallback(this.value)
                    // 考虑到这里的传值的不确定性，除了时普通值（非Promise对象实例），也有可能传进来的是一个Promise对象实例，
                    // 如果是Promise对象实例，那么我们需要等待结果返回再将其作为参数值传递给下一个then方法的回调
                    // 从而避免结果的层层嵌套，导致最终结果无法正确解析出来进行正确处理
                    // resolve(sc)
                    // 这里是为了让promiseTemp能够正常获取到
                    setTimeout(() => {
                        try {
                            // let sc = isFunc ? successCallback(this.value) : this.value
                            let sc = successCallback(this.value)
                            resolvePromise(promiseTemp, sc, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0)
                    break;
                case REJECTED:
                    // isFunc = failCallback instanceof Function
                    // 失败的时候调用失败回调，把失败的原因传进去
                    // 若果不是function，就直接把上一个promise的结果值续传下去
                    // let fc = isFunc ? failCallback(this.reason) : this.reason
                    // let fc = failCallback(this.reason)

                    setTimeout(() => {
                        try {
                            let fc = failCallback(this.reason)
                            resolvePromise(promiseTemp, fc, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0)
                    // reject(fc)
                    break;
                case PENDING:
                    // 如果执行器时异步的情况下，执行器的立即执行并没有立即监测到promise状态status的改变
                    // 所以这里需要对该情况进行处理，当pending状态并为发生改变时，
                    // 我们需要等待异步结果的返回再根据结果分别调用成功回调或者失败回调
                    // 那么我们就需要把异步情况下的成功回调和失败回调暂存起来，供异步处理完成后再拿出来进行调用。
                    // 即执行器中resolve被调用时调用成功回调this.successCallback，reject被调用时则调用失败回调this.failCallback
                    this.successCallback.push(() => {
                        try {
                            // let sc = isFunc ? successCallback(this.value) : this.value
                            let sc = successCallback(this.value)
                            resolvePromise(promiseTemp, sc, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    })
                    this.failCallback.push(() => {
                        try {
                            let fc = failCallback(this.reason)
                            resolvePromise(promiseTemp, fc, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    })
                //能否把successCallback和failCallback的判断前置，如何续传value
                // this.pengdingResolves.push(resolve)
                // this.pengdingRejects.push(reject)
            }
        })

        return promiseTemp
    }
    /**
     * 1、可以处理当前promise最终最状态为失败的情况
     * 如果then函数中没有传失败的回调的话，那最好最终可以被catch方法捕获并进行处理
     * 2、也就是说当我们调用then方法的时候是可以不传递失败回调的。
     * @param {*} failCallback 
     */
    catch(failCallback) {
        return this.then(undefined, failCallback)
    }
    /**
     * 两个关键特点：
     * 1、无论promise是成功还是失败，finally中的回调都会被执行一次。
     * 2、finally最终也是返回一个promise对象，可以继续调用then操作进行链式调用
     */
    finally(callback) {
        // 如果callback中有返回值，那么该值也需要传递给下一个then的回调函数
        return this.then(value => {
            // callback()
            // return value
            return MyPromise.resolve(callback()).then(() => value)
        }, reason => {
            // callback()
            // throw reason
            return MyPromise.resolve(callback()).then(() => { throw reason })
        })
    }
    /**
     * 该方法是一个静态方法，
     * 它需要传入一个数组参数，数组元素可以是任意值，也可以是promise对象，
     * 它只要有一个结果成功或者失败，就马上返回该成功结果或者失败结果
     * 返回的最终结果也是一个Promise对象，可以进行链式调用
     * 相当于是赛马机制，永远第一个知道结果的
     * @param {*} array 
     */
    static race(array) {
        let done = false;
        return new MyPromise((resolve, reject) => {
            for (let index = 0; !done && index < array.length; index++) {
                const current = array[index];
                // 这里需要判断当前的元素是否为异步的promise对象，
                // 如果是的话需要对它进行查看返回结果再push到result结果数组中
                if (current instanceof MyPromise) {
                    // promise对象
                    current.then(value => {
                        done = true
                        resolve(value)
                        return
                    }, reason => {
                        done = true
                        reject(reason)
                        return
                    })
                } else {
                    // 普通值
                    done = true
                    resolve(current)
                    return
                }

            }
        })
    }
    /**
     * 该方法是一个静态方法，
     * 它需要传入一个数组参数，数组元素可以是任意值，也可以是promise对象，
     * 返回结果按照传入的数组元素的顺序得到相同顺序的结果，
     * 返回的最终结果也是一个Promise对象，可以进行链式调用
     * 只有全部计算结果均成功的时候，它才能成功返回，否则失败
     */
    static all(array) {
        // 定义一个数组用于暂存遍历数组计算结果，并最终在封装的Promise对象中通过resolve返回
        let result = [];
        // 用于判断是否全部数组元素都已经计算得到结果，没得到一个结果该值+1直至等于数组长度了就可以返回了
        let index = 0;
        return new MyPromise((resolve, reject) => {
            function addData(key, value) {
                console.log(key, value)
                result[key] = value
                index++;
                if (index === array.length) resolve(result)
            }
            for (let index = 0; index < array.length; index++) {
                const current = array[index];
                // 这里需要判断当前的元素是否为异步的promise对象，
                // 如果是的话需要对它进行查看返回结果再push到result结果数组中
                if (current instanceof MyPromise) {
                    // promise对象
                    current.then(value => addData(index, value), reason => reject(reason))
                } else {
                    // 普通值
                    addData(index, current)
                }

            }
        })

    }
}

/**
 * then方法中可复用代码抽取出来单独写成方法。
 * @param {*} sourcePromise 
 * @param {*} valueX 
 * @param {*} resolve 
 * @param {*} reject 
 * @returns 
 */
function resolvePromise(sourcePromise, valueX, resolve, reject) {
    if (sourcePromise === valueX) {
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    valueX instanceof MyPromise ? valueX.then(resolve, reject) : resolve(valueX)
}

module.exports = MyPromise